- Senior Software Engineer
- CDI co-spec lead
- Red Hat, Inc.
- @antoine_sd
- www.next-presso.com
- github.com/antoinesd
One of the most powerful feature of the CDI specification
Not really popularized in partly due to the high level of abstraction
Observer pattern to listen for container initialization lifecycle events
Comprehensive access to and modification of the container metadata model
Service provider of the service javax.enterprise.inject.spi.Extension declared in META-INF/services |
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.Extension;
class CdiExtension implements Extension {
void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd) {
}
...
void afterDeploymentValidation(@Observes AfterDeploymentValidation adv) {
}
}Injection points, parameterized types, programmatic bean
| A functional interface: |
public interface Transformer<I, O> {
O transform(I input);
}| And a CDI qualifier with transformation metadata: |
import javax.enterprise.util.Nonbinding;
import javax.inject.Qualifier;
@Qualifier
public @interface Transformation {
@Nonbinding
String value() default ""; // The transformation name
}@Inject
@Transformation("transformation")
Transformer<InputType, OutputType> transformer;
InputType input;
OutputType result = transformer.transform(input);| Distribute configuration closest to the code: |
@Inject
@Transformation("transformationi")
Transformer<InputTypei, OutputTypei> transformeri;
...
@Inject
@Transformation("transformationj")
Transformer<InputTypej, OutputTypej> transformerj;ProcessInjectionPoint EventCollect all the @Transformation metadata required to instantiate the legacy component |
public interface ProcessInjectionPoint<T, X> {
public InjectionPoint getInjectionPoint();
public void setInjectionPoint(InjectionPoint injectionPoint);
public void addDefinitionError(Throwable t);
}class TransformationExtension implements Extension {
Set<String> transformations = new HashSet<>();
void collectConfiguration(@Observes ProcessInjectionPoint<?, Transformer> pit) { (1)
Annotated annotated = pit.getInjectionPoint().getAnnotated();
if (annotated.isAnnotationPresent(Transformation.class)) {
transformations.add(annotated.getAnnotation(Transformation.class).value()); (2)
}
}
}| 1 | Observe every Transformer injection point on any declaring bean |
| 2 | Collect the injection point @Transformation metadata |
Bean Interface| Integrate the legacy component as a CDI Bean |
public interface Bean<T> extends Contextual<T>, BeanAttributes<T> {
public Class<?> getBeanClass();
public Set<InjectionPoint> getInjectionPoints();
// Contextual<T>
public T create(CreationalContext<T> creationalContext);
public void destroy(T instance, CreationalContext<T> creationalContext);
// BeanAttributes<T>
public Set<Type> getTypes();
public Set<Annotation> getQualifiers();
public Class<? extends Annotation> getScope();
public String getName();
public Set<Class<? extends Annotation>> getStereotypes();
public boolean isAlternative();
}class LegacyTransformerBean implements Bean<LegacyTransformer> {
Set<String> transformations = new HashSet<>();
LegacyTransformerBean(Set<String> transformations) {
this.transformations = transformations;
}
public LegacyTransformer create(CreationalContext<LegacyTransformer> context) {
LegacyTransformer transformer = new LegacyTransformer();
tranformer.load(transformations);
return LegacyTransformer;
}
public Set<Annotation> getQualifiers() {
return Collections.unmodifiableSet(new HashSet<>(
Arrays.asList(DefaultLiteral.INSTANCE, AnyLiteral.INSTANCE)));
}
public Class<? extends Annotation> getScope() {
return ApplicationScoped.class;
}
}AfterBeanDiscovery Event| Add the legacy component bean after bean discovery |
public interface AfterBeanDiscovery {
public void addDefinitionError(Throwable t);
public void addBean(Bean<?> bean);
public void addObserverMethod(ObserverMethod<?> observerMethod);
public void addContext(Context context);
public <T> AnnotatedType<T> getAnnotatedType(Class<T> type, String id);
public <T> Iterable<AnnotatedType<T>> getAnnotatedTypes(Class<T> type);
}class TransformationExtension implements Extension {
Set<String> transformations = new HashSet<>();
void collectConfiguration(@Observes ProcessInjectionPoint<?, Transformer> pit) {
Annotated annotated = pit.getInjectionPoint().getAnnotated();
if (annotated.isAnnotationPresent(Transformation.class))
transformations.add(annotated.getAnnotation(Transformation.class).value());
}
void addLegacyTransformerBean(@Observes AfterBeanDiscovery abd) {
LegacyTransformer legacyTransformer = new LegacyTransformer(transformations);
abd.addBean(new LegacyTransformerBean>(transformations));
}
}| Parameterized types are not erased by CDI |
@Produces
@Transformation
<I, O> Transformer<I, O> legacyTransformerFacade(LegacyTransformer legacyTransformer,
InjectionPoint injectionPoint) {
Transformation transformation = getQualifierByType(injectionPoint.getQualifiers(),
Transformation.class);
return new LegacyTransformerFacade<I, O>(legacyTransformer, transformation.value(),
injectionPoint.getType()));
}Annotated types, events, injection Targets, transactional Observers
| Open-source integration framework based on known Enterprise Integration Patterns |
| Bean binding and integration with Spring, Blueprint, Guice and CDI |
@EndpointInject(uri="jms:queue:foo")
Endpoint endpoint;
@PropertyInject(value = "timeout", defaultValue = "5000")
int timeout;
@BeanInject("foo")
FooBean foo;
@Produce(uri = "mock:foo")
ProducerTemplate producer;
@Consume(uri="jms:queue:foo")
void onFoo(@Body String body) {
}| Bring support for both Camel and CDI beans… |
ProcessAnnotatedType EventAnnotatedType<X>public interface AnnotatedType<X> extends Annotated {
public Class<X> getJavaClass();
public Set<AnnotatedConstructor<X>> getConstructors();
public Set<AnnotatedMethod<? super X>> getMethods();
public Set<AnnotatedField<? super X>> getFields();
}ProcessAnnotatedType<X>public interface ProcessAnnotatedType<X> {
public AnnotatedType<X> getAnnotatedType();
public void setAnnotatedType(AnnotatedType<X> type);
public void veto();
}ProcessInjectionTarget EventInjectionTarget<T>public interface InjectionTarget<T> extends Producer<T> {
public void inject(T instance, CreationalContext<T> ctx);
public void postConstruct(T instance);
public void preDestroy(T instance);
}ProcessInjectionTarget<T>public interface ProcessInjectionTarget<X> {
public AnnotatedType<X> getAnnotatedType();
public InjectionTarget<X> getInjectionTarget();
public void setInjectionTarget(InjectionTarget<X> injectionTarget);
public void addDefinitionError(Throwable t);
}class CdiCamelExtension implements Extension {
Set<AnnotatedType<?>> camelBeans = new HashSet<>());
void camelAnnotations(@Observes @WithAnnotations({BeanInject.class, (1)
Consume.class, EndpointInject.class, Produce.class, PropertyInject.class})
ProcessAnnotatedType<?> pat) {
camelBeans.add(pat.getAnnotatedType());
}
<T> void camelBeansPostProcessor(@Observes ProcessInjectionTarget<T> pit) {
if (camelBeans.contains(pit.getAnnotatedType())) (2)
pit.setInjectionTarget(new CamelInjectionTarget<>(pit.getInjectionTarget()));
}
}| 1 | Detect all the types containing Camel annotations with @WithAnnotations |
| 2 | Decorate the InjectionTarget corresponding to these types with a custom post-processor |
InjectionTarget Decorationclass CamelInjectionTarget<T> implements InjectionTarget<T> {
InjectionTarget<T> delegate;
DefaultCamelBeanPostProcessor processor;
CamelInjectionTarget(InjectionTarget<T> target) {
delegate = target;
processor = new DefaultCamelBeanPostProcessor();
}
@Override
public void inject(T instance, CreationalContext<T> ctx) {
delegate.inject(instance, ctx);
processor.postProcessBeforeInitialization(instance); (1)
}
}| 1 | Call the Camel default bean post-processor after CDI injection |
from("jms:queue:{{input}}?transactionManager=#jtaTM")
.id("Input Consumer")
.onException().log("Rolling back message with ID ${header.JMSMessageID}")
.rollback().id("Rollback Transaction")
.end()
.log("Receiving message with ID ${header.JMSMessageID}: ${body}")
.choice()
.when(header("JMSRedelivered").isEqualTo(Boolean.TRUE))
.to("jms:queue:{{error}}?transactionManager=#jtaTM").id("Error Producer")
.otherwise()
.beanRef("transformer").id("Transformer")
.to("murex:trade-repository").id("Trade Repository")
.choice()
.when(not(isInserted))
.log("Error received: ${body}").id("Trade Repository Error")
.throwException(new CamelExecutionException("Import Failed")))
.otherwise()
.log("Answer received: ${body}").id("Trade Repository Answer");| Camel DSL Aspect Oriented Programming with CDI observer methods as pointcut and advice definitions |
void interceptProcessor(@Observes @Before @Node("foo") Exchange exchange) {
// intercept the exchange before processor with id "foo"
}void interceptProcessorBody(@Observes @Node("foo") @Body String body) {
// use Camel parameter binding annotations for the joint point context
}void receive(@Observes(during=AFTER_SUCCESS) @Endpoint("bar") Exchange exchange) {
// exchange sent to endpoint "bar" when the transaction is committed successfully
}ProcessObserverMethod EventObserverMethod<T>public interface ObserverMethod<T> {
public Class<?> getBeanClass();
public Type getObservedType();
public Set<Annotation> getObservedQualifiers();
public Reception getReception();
public TransactionPhase getTransactionPhase();
public void notify(T event);
}ProcessObserverMethod<T, X>public interface ProcessObserverMethod<T, X> {
public AnnotatedMethod<X> getAnnotatedMethod();
public ObserverMethod<T> getObserverMethod();
public void addDefinitionError(Throwable t);
}Annotated types, alternatives, interceptors, producers
Open-source Java library providing monitoring primitives like Counter, Gauge, Histogram, Meter, Timer, … |
Provides a MetricRegistry that articulates modules and reporters |
| Defines annotations for AOP frameworks like Spring AOP, AspectJ, Guice (AOP Alliance) and CDI, e.g.: |
class TimedMethodBean {
@Timed
void timedMethod() {
// Timer name => TimedMethodBean.timedMethod
}
}| Use interceptors for Metrics annotation AOP |
class MetricsExtension implements Extension {
<X> void interceptTimedMethod(@Observes @WithAnnotations(Timed.class)
ProcessAnnotatedType<X> pat) {
Set<AnnotatedMethod<? super X>> decoratedMethods = new HashSet<>();
for (AnnotatedMethod<? super X> method : pat.getAnnotatedType().getMethods())
if (method.isAnnotationPresent(Timed.class))
decoratedMethods.add(
new AnnotatedMethodDecorator<>(method, new TimedBindingLiteral()));
pat.setAnnotatedType(
new AnnotatedTypeDecorator<>(pat.getAnnotatedType(), decoratedMethods));
}
}@Interceptor
@TimedBinding
@Priority(Interceptor.Priority.LIBRARY_BEFORE)
class TimedInterceptor {
@Inject MetricRegistry registry;
@AroundInvoke
Object timedMethod(InvocationContext context) throws Exception {
String name = context.getMethod().getAnnotation(Timed.class).name();
Timer timer = registry.timer(name);
Timer.Context time = timer.time();
try {
return context.proceed();
} finally {
time.stop();
}
}
}ProcessBean<X> Events| Use producer fields / methods to register custom metrics |
@Produces
Timer Timer = new Timer(new SlidingTimeWindowReservoir(1L, TimeUnit.MINUTES));ProcessProducerMethod<T, X>public interface ProcessProducerMethod<T, X> extends ProcessBean<X> {
public AnnotatedMethod<T> getAnnotatedProducerMethod();
public AnnotatedParameter<T> getAnnotatedDisposedParameter();
// ProcessBean<X>
public Annotated getAnnotated();
public Bean<X> getBean();
}ProcessManagedBean<X> and ProcessProducerField<T, X> are fired for managed beans and producer fields respectively |
AfterDeploymentValidation Eventclass MetricsExtension implements Extension {
Map<Bean<?>, AnnotatedMember<?>> metrics = new HashMap<>();
void producerFields(@Observes ProcessProducerField<? extends Metric, ?> ppf) {
metrics.put(ppf.getBean(), ppf.getAnnotatedProducerField()); (1)
}
void producerMethods(@Observes ProcessProducerMethod<? extends Metric, ?> ppm) {
metrics.put(ppm.getBean(), ppm.getAnnotatedProducerMethod()); (1)
}
void customMetrics(@Observes AfterDeploymentValidation adv, BeanManager manager) {
for (Map.Entry<Bean<?>, AnnotatedMember<?>> metric : metrics.entrySet())
registry.register(metricName(member), manager.getReference(metric.getKey(), (2)
metric.getValue().getBaseType(), manager.createCreationalContext(null)));
}
}| 1 | Collect the custom Metric producer fields and methods |
| 2 | Instantiate the custom metrics into the Metrics registry |
@Inject
private Meter hits; (1)
@Timed(name = "calls") (2)
public void cachedMethod() {
if (hit) hits.mark();
}
@Produces @Metric(name = "cache-hits") (3)
private Gauge<Double> cacheHitRatioGauge(Meter hits, Timer calls) {
return () -> calls.getOneMinuteRate() == 0 ? Double.NaN :
hits.getOneMinuteRate() / calls.getOneMinuteRate();
}| 1 | Metric injection from the registry |
| 2 | Method instrumentation with CDI interceptors |
| 3 | Produce a custom Metric instance by composing others |
| Slides generated with Asciidoctor, PlantUML and DZSlides backend |
| Original slide template - Dan Allen & Sarah White |
| Camel CDI Extension - github.com/astefanutti/camel-cdi |
| Metrics CDI Extension - github.com/astefanutti/metrics-cdi |